// original execCommand
function _nativeCommand(doc, key, val) {
  try {
    doc.execCommand(key, false, val);
  } catch (e) {}
}
// original queryCommandValue
function _nativeCommandValue(doc, key) {
  var val = "";
  try {
    val = doc.queryCommandValue(key);
  } catch (e) {}
  if (typeof val !== "string") {
    val = "";
  }
  return val;
}
// get current selection of a document
function _getSel(doc) {
  var win = _getWin(doc);
  return _IERANGE ? doc.selection : win.getSelection();
}
// get range of current selection
function _getRng(doc) {
  var sel = _getSel(doc),
    rng;
  try {
    if (sel.rangeCount > 0) {
      rng = sel.getRangeAt(0);
    } else {
      rng = sel.createRange();
    }
  } catch (e) {}
  if (
    _IERANGE &&
    (!rng || (!rng.item && rng.parentElement().ownerDocument !== doc))
  ) {
    return null;
  }
  return rng;
}
//将map的复合key转换成单一key
function _singleKeyMap(map) {
  var newMap = {},
    arr,
    v;
  _each(map, function(key, val) {
    arr = key.split(",");
    for (var i = 0, len = arr.length; i < len; i++) {
      v = arr[i];
      newMap[v] = val;
    }
  });
  return newMap;
}
//判断一个node是否有指定属性或CSS
function _hasAttrOrCss(knode, map) {
  return _hasAttrOrCssByKey(knode, map, "*") || _hasAttrOrCssByKey(knode, map);
}
function _hasAttrOrCssByKey(knode, map, mapKey) {
  mapKey = mapKey || knode.name;
  if (knode.type !== 1) {
    return false;
  }
  var newMap = _singleKeyMap(map);
  if (!newMap[mapKey]) {
    return false;
  }
  var arr = newMap[mapKey].split(",");
  for (var i = 0, len = arr.length; i < len; i++) {
    var key = arr[i];
    if (key === "*") {
      return true;
    }
    var match = /^(\.?)([^=]+)(?:=([^=]*))?$/.exec(key);
    var method = match[1] ? "css" : "attr";
    key = match[2];
    var val = match[3] || "";
    if (val === "" && knode[method](key) !== "") {
      return true;
    }
    if (val !== "" && knode[method](key) === val) {
      return true;
    }
  }
  return false;
}
//删除一个node的属性和CSS
function _removeAttrOrCss(knode, map) {
  if (knode.type != 1) {
    return;
  }
  _removeAttrOrCssByKey(knode, map, "*");
  _removeAttrOrCssByKey(knode, map);
}
function _removeAttrOrCssByKey(knode, map, mapKey) {
  mapKey = mapKey || knode.name;
  if (knode.type !== 1) {
    return;
  }
  var newMap = _singleKeyMap(map);
  if (!newMap[mapKey]) {
    return;
  }
  var arr = newMap[mapKey].split(","),
    allFlag = false;
  for (var i = 0, len = arr.length; i < len; i++) {
    var key = arr[i];
    if (key === "*") {
      allFlag = true;
      break;
    }
    var match = /^(\.?)([^=]+)(?:=([^=]*))?$/.exec(key);
    key = match[2];
    if (match[1]) {
      key = _toCamel(key);
      if (knode[0].style[key]) {
        knode[0].style[key] = "";
      }
    } else {
      knode.removeAttr(key);
    }
  }
  if (allFlag) {
    knode.remove(true);
  }
}
//取得最里面的element
function _getInnerNode(knode) {
  var inner = knode;
  while (inner.first()) {
    inner = inner.first();
  }
  return inner;
}
//最里面的element为inline element时返回true
function _isEmptyNode(knode) {
  if (knode.type != 1 || knode.isSingle()) {
    return false;
  }
  return knode.html().replace(/<[^>]+>/g, "") === "";
}
//merge two wrapper
//a : <span><strong></strong></span>
//b : <strong><em></em></strong>
//result : <span><strong><em></em></strong></span>
function _mergeWrapper(a, b) {
  a = a.clone(true);
  var lastA = _getInnerNode(a),
    childA = a,
    merged = false;
  while (b) {
    while (childA) {
      if (childA.name === b.name) {
        _mergeAttrs(childA, b.attr(), b.css());
        merged = true;
      }
      childA = childA.first();
    }
    if (!merged) {
      lastA.append(b.clone(false));
    }
    merged = false;
    b = b.first();
  }
  return a;
}
//wrap and merge a node
function _wrapNode(knode, wrapper) {
  wrapper = wrapper.clone(true);
  //node为text node时
  if (knode.type == 3) {
    _getInnerNode(wrapper).append(knode.clone(false));
    knode.replaceWith(wrapper);
    return wrapper;
  }
  //node为element时
  //取得node的wrapper
  var nodeWrapper = knode,
    child;
  while ((child = knode.first()) && child.children().length == 1) {
    knode = child;
  }
  //将node的子节点纳入在一个documentFragment里
  child = knode.first();
  var frag = knode.doc.createDocumentFragment();
  while (child) {
    frag.appendChild(child[0]);
    child = child.next();
  }
  wrapper = _mergeWrapper(nodeWrapper, wrapper);
  if (frag.firstChild) {
    _getInnerNode(wrapper).append(frag);
  }
  nodeWrapper.replaceWith(wrapper);
  return wrapper;
}
//merge attributes and styles
function _mergeAttrs(knode, attrs, styles) {
  _each(attrs, function(key, val) {
    if (key !== "style") {
      knode.attr(key, val);
    }
  });
  _each(styles, function(key, val) {
    knode.css(key, val);
  });
}
// 判断node是否在pre、style、script里
function _inPreElement(knode) {
  while (knode && knode.name != "body") {
    if (
      _PRE_TAG_MAP[knode.name] ||
      (knode.name == "div" && knode.hasClass("ke-script"))
    ) {
      return true;
    }
    knode = knode.parent();
  }
  return false;
}
// create KCmd class
function KCmd(range) {
  this.init(range);
}
_extend(KCmd, {
  init: function(range) {
    var self = this,
      doc = range.doc;
    self.doc = doc;
    self.win = _getWin(doc);
    self.sel = _getSel(doc);
    self.range = range;
  },
  selection: function(forceReset) {
    var self = this,
      doc = self.doc,
      rng = _getRng(doc);
    self.sel = _getSel(doc);
    if (rng) {
      self.range = _range(rng);
      if (K(self.range.startContainer).name == "html") {
        self.range.selectNodeContents(doc.body).collapse(false);
      }
      return self;
    }
    if (forceReset) {
      self.range.selectNodeContents(doc.body).collapse(false);
    }
    return self;
  },
  select: function(hasDummy) {
    hasDummy = _undef(hasDummy, true);
    var self = this,
      sel = self.sel,
      range = self.range.cloneRange().shrink(),
      sc = range.startContainer,
      so = range.startOffset,
      ec = range.endContainer,
      eo = range.endOffset,
      doc = _getDoc(sc),
      win = self.win,
      rng,
      hasU200b = false;
    // tag内部无内容时选中tag内部，<tagName>[]</tagName>
    if (hasDummy && sc.nodeType == 1 && range.collapsed) {
      if (_IERANGE) {
        var dummy = K("<span>&nbsp;</span>", doc);
        range.insertNode(dummy[0]);
        rng = doc.body.createTextRange();
        try {
          rng.moveToElementText(dummy[0]);
        } catch (ex) {}
        rng.collapse(false);
        rng.select();
        dummy.remove();
        win.focus();
        return self;
      }
      if (_WEBKIT) {
        var children = sc.childNodes;
        if (
          K(sc).isInline() ||
          (so > 0 && K(children[so - 1]).isInline()) ||
          (children[so] && K(children[so]).isInline())
        ) {
          range.insertNode(doc.createTextNode("\u200B"));
          hasU200b = true;
        }
      }
    }
    //other case
    if (_IERANGE) {
      try {
        rng = range.get(true);
        rng.select();
      } catch (e) {}
    } else {
      if (hasU200b) {
        range.collapse(false);
      }
      rng = range.get(true);
      sel.removeAllRanges();
      sel.addRange(rng);
      // Bugfix: https://github.com/kindsoft/kindeditor/issues/54
      if (doc !== document) {
        var pos = K(rng.endContainer).pos();
        win.scrollTo(pos.x, pos.y);
      }
    }
    win.focus();
    return self;
  },
  wrap: function(val) {
    var self = this,
      doc = self.doc,
      range = self.range,
      wrapper;
    wrapper = K(val, doc);
    // collapsed=true
    if (range.collapsed) {
      range.shrink();
      range.insertNode(wrapper[0]).selectNodeContents(wrapper[0]);
      return self;
    }
    // block wrapper
    if (wrapper.isBlock()) {
      var copyWrapper = wrapper.clone(true),
        child = copyWrapper;
      // find inner element
      while (child.first()) {
        child = child.first();
      }
      child.append(range.extractContents());
      range.insertNode(copyWrapper[0]).selectNode(copyWrapper[0]);
      return self;
    }
    // collapsed=false
    range.enlarge();
    var bookmark = range.createBookmark(),
      ancestor = range.commonAncestor(),
      isStart = false;
    K(ancestor).scan(function(node) {
      if (!isStart && node == bookmark.start) {
        isStart = true;
        return;
      }
      if (isStart) {
        if (node == bookmark.end) {
          return false;
        }
        var knode = K(node);
        if (_inPreElement(knode)) {
          return;
        }
        if (knode.type == 3 && _trim(node.nodeValue).length > 0) {
          // textNode为唯一的子节点时，重新设置node
          var parent;
          while (
            (parent = knode.parent()) &&
            parent.isStyle() &&
            parent.children().length == 1
          ) {
            knode = parent;
          }
          _wrapNode(knode, wrapper);
        }
      }
    });
    range.moveToBookmark(bookmark);
    return self;
  },
  split: function(isStart, map) {
    var range = this.range,
      doc = range.doc;
    //get parent node
    var tempRange = range.cloneRange().collapse(isStart);
    var node = tempRange.startContainer,
      pos = tempRange.startOffset,
      parent = node.nodeType == 3 ? node.parentNode : node,
      needSplit = false,
      knode;
    while (parent && parent.parentNode) {
      knode = K(parent);
      if (map) {
        if (!knode.isStyle()) {
          break;
        }
        if (!_hasAttrOrCss(knode, map)) {
          break;
        }
      } else {
        if (_NOSPLIT_TAG_MAP[knode.name]) {
          break;
        }
      }
      needSplit = true;
      parent = parent.parentNode;
    }
    //split parent node
    if (needSplit) {
      var dummy = doc.createElement("span");
      range
        .cloneRange()
        .collapse(!isStart)
        .insertNode(dummy);
      if (isStart) {
        tempRange.setStartBefore(parent.firstChild).setEnd(node, pos);
      } else {
        tempRange.setStart(node, pos).setEndAfter(parent.lastChild);
      }
      var frag = tempRange.extractContents(),
        first = frag.firstChild,
        last = frag.lastChild;
      if (isStart) {
        tempRange.insertNode(frag);
        range.setStartAfter(last).setEndBefore(dummy);
      } else {
        parent.appendChild(frag);
        range.setStartBefore(dummy).setEndBefore(first);
      }
      //调整endOffset
      var dummyParent = dummy.parentNode;
      if (dummyParent == range.endContainer) {
        var prev = K(dummy).prev(),
          next = K(dummy).next();
        if (prev && next && prev.type == 3 && next.type == 3) {
          //dummy元素的左右都是textNode，<strong>f<span></span>g</strong>
          range.setEnd(prev[0], prev[0].nodeValue.length);
        } else if (!isStart) {
          range.setEnd(range.endContainer, range.endOffset - 1);
        }
      }
      dummyParent.removeChild(dummy);
    }
    return this;
  },
  remove: function(map) {
    var self = this,
      doc = self.doc,
      range = self.range;
    range.enlarge();
    // <p><strong><em>[123456789]</em></strong></p>, remove strong
    if (range.startOffset === 0) {
      var ksc = K(range.startContainer),
        parent;
      while (
        (parent = ksc.parent()) &&
        parent.isStyle() &&
        parent.children().length == 1
      ) {
        ksc = parent;
      }
      range.setStart(ksc[0], 0);
      // <p style="color:red;">[abcd</p>, remove style
      ksc = K(range.startContainer);
      if (ksc.isBlock()) {
        _removeAttrOrCss(ksc, map);
      }
      var kscp = ksc.parent();
      if (kscp && kscp.isBlock()) {
        _removeAttrOrCss(kscp, map);
      }
    }
    var sc, so;
    // collapsed == true
    if (range.collapsed) {
      self.split(true, map);
      // remove empty element
      sc = range.startContainer;
      so = range.startOffset;
      if (so > 0) {
        var sb = K(sc.childNodes[so - 1]);
        if (sb && _isEmptyNode(sb)) {
          sb.remove();
          range.setStart(sc, so - 1);
        }
      }
      var sa = K(sc.childNodes[so]);
      if (sa && _isEmptyNode(sa)) {
        sa.remove();
      }
      // <strong>|</strong>
      if (_isEmptyNode(sc)) {
        range.startBefore(sc);
        sc.remove();
      }
      range.collapse(true);
      return self;
    }
    // split range
    self.split(true, map);
    self.split(false, map);
    // insert dummy element
    var startDummy = doc.createElement("span"),
      endDummy = doc.createElement("span");
    range
      .cloneRange()
      .collapse(false)
      .insertNode(endDummy);
    range
      .cloneRange()
      .collapse(true)
      .insertNode(startDummy);
    // select element
    var nodeList = [],
      cmpStart = false;
    K(range.commonAncestor()).scan(function(node) {
      if (!cmpStart && node == startDummy) {
        cmpStart = true;
        return;
      }
      if (node == endDummy) {
        return false;
      }
      if (cmpStart) {
        nodeList.push(node);
      }
    });
    // remove dummy element
    K(startDummy).remove();
    K(endDummy).remove();
    // remove empty element
    sc = range.startContainer;
    so = range.startOffset;
    var ec = range.endContainer,
      eo = range.endOffset;
    if (so > 0) {
      var startBefore = K(sc.childNodes[so - 1]);
      if (startBefore && _isEmptyNode(startBefore)) {
        startBefore.remove();
        range.setStart(sc, so - 1);
        if (sc == ec) {
          range.setEnd(ec, eo - 1);
        }
      }
      // <b>abc[</b><b>def]</b><b>ghi</b>，分割后HTML变成
      // <b>abc</b>[<b></b><b>def</b>]<b>ghi</b>
      var startAfter = K(sc.childNodes[so]);
      if (startAfter && _isEmptyNode(startAfter)) {
        startAfter.remove();
        if (sc == ec) {
          range.setEnd(ec, eo - 1);
        }
      }
    }
    var endAfter = K(ec.childNodes[range.endOffset]);
    if (endAfter && _isEmptyNode(endAfter)) {
      endAfter.remove();
    }
    var bookmark = range.createBookmark(true);
    // remove attributes or styles
    _each(nodeList, function(i, node) {
      _removeAttrOrCss(K(node), map);
    });
    range.moveToBookmark(bookmark);
    return self;
  },
  commonNode: function(map) {
    var range = this.range;
    var ec = range.endContainer,
      eo = range.endOffset,
      node = ec.nodeType == 3 || eo === 0 ? ec : ec.childNodes[eo - 1];
    function find(node) {
      var child = node,
        parent = node;
      while (parent) {
        if (_hasAttrOrCss(K(parent), map)) {
          return K(parent);
        }
        parent = parent.parentNode;
      }
      while (child && (child = child.lastChild)) {
        if (_hasAttrOrCss(K(child), map)) {
          return K(child);
        }
      }
      return null;
    }
    var cNode = find(node);
    if (cNode) {
      return cNode;
    }
    //<strong>123</strong>|4567
    //<strong>123</strong>|<br />
    if (node.nodeType == 1 || (ec.nodeType == 3 && eo === 0)) {
      var prev = K(node).prev();
      if (prev) {
        return find(prev);
      }
    }
    return null;
  },
  commonAncestor: function(tagName) {
    var range = this.range,
      sc = range.startContainer,
      so = range.startOffset,
      ec = range.endContainer,
      eo = range.endOffset,
      startNode = sc.nodeType == 3 || so === 0 ? sc : sc.childNodes[so - 1],
      endNode = ec.nodeType == 3 || eo === 0 ? ec : ec.childNodes[eo - 1];
    function find(node) {
      while (node) {
        if (node.nodeType == 1) {
          if (node.tagName.toLowerCase() === tagName) {
            return node;
          }
        }
        node = node.parentNode;
      }
      return null;
    }
    var start = find(startNode),
      end = find(endNode);
    if (start && end && start === end) {
      return K(start);
    }
    return null;
  },
  // Reference: document.queryCommandState
  // TODO
  state: function(key) {
    var self = this,
      doc = self.doc,
      bool = false;
    try {
      bool = doc.queryCommandState(key);
    } catch (e) {}
    return bool;
  },
  // Reference: document.queryCommandValue
  val: function(key) {
    var self = this,
      doc = self.doc,
      range = self.range;
    function lc(val) {
      return val.toLowerCase();
    }
    key = lc(key);
    var val = "",
      knode;
    if (key === "fontfamily" || key === "fontname") {
      val = _nativeCommandValue(doc, "fontname");
      val = val.replace(/['"]/g, "");
      return lc(val);
    }
    if (key === "formatblock") {
      val = _nativeCommandValue(doc, key);
      if (val === "") {
        knode = self.commonNode({ "h1,h2,h3,h4,h5,h6,p,div,pre,address": "*" });
        if (knode) {
          val = knode.name;
        }
      }
      if (val === "Normal") {
        val = "p";
      }
      return lc(val);
    }
    if (key === "fontsize") {
      knode = self.commonNode({ "*": ".font-size" });
      if (knode) {
        val = knode.css("font-size");
      }
      return lc(val);
    }
    if (key === "forecolor") {
      knode = self.commonNode({ "*": ".color" });
      if (knode) {
        val = knode.css("color");
      }
      val = _toHex(val);
      if (val === "") {
        val = "default";
      }
      return lc(val);
    }
    if (key === "hilitecolor") {
      knode = self.commonNode({ "*": ".background-color" });
      if (knode) {
        val = knode.css("background-color");
      }
      val = _toHex(val);
      if (val === "") {
        val = "default";
      }
      return lc(val);
    }
    return val;
  },
  toggle: function(wrapper, map) {
    var self = this;
    if (self.commonNode(map)) {
      self.remove(map);
    } else {
      self.wrap(wrapper);
    }
    return self.select();
  },
  bold: function() {
    return this.toggle("<strong></strong>", {
      span: ".font-weight=bold",
      strong: "*",
      b: "*"
    });
  },
  italic: function() {
    return this.toggle("<em></em>", {
      span: ".font-style=italic",
      em: "*",
      i: "*"
    });
  },
  underline: function() {
    return this.toggle("<u></u>", {
      span: ".text-decoration=underline",
      u: "*"
    });
  },
  strikethrough: function() {
    return this.toggle("<s></s>", {
      span: ".text-decoration=line-through",
      s: "*"
    });
  },
  forecolor: function(val) {
    return this.wrap('<span style="color:' + val + ';"></span>').select();
    // return this.toggle('<span style="color:' + val + ';"></span>', {
    // 	span : '.color=' + val,
    // 	font : 'color'
    // });
  },
  hilitecolor: function(val) {
    return this.wrap(
      '<span style="background-color:' + val + ';"></span>'
    ).select();
    // return this.toggle('<span style="background-color:' + val + ';"></span>', {
    // 	span : '.background-color=' + val
    // });
  },
  fontsize: function(val) {
    return this.wrap('<span style="font-size:' + val + ';"></span>').select();
    // return this.toggle('<span style="font-size:' + val + ';"></span>', {
    // 	span : '.font-size=' + val,
    // 	font : 'size'
    // });
  },
  fontname: function(val) {
    return this.fontfamily(val);
  },
  fontfamily: function(val) {
    return this.wrap('<span style="font-family:' + val + ';"></span>').select();
    // return this.toggle('<span style="font-family:' + val + ';"></span>', {
    // 	span : '.font-family=' + val,
    // 	font : 'face'
    // });
  },
  removeformat: function() {
    var map = {
        "*":
          ".font-weight,.font-style,.text-decoration,.color,.background-color,.font-size,.font-family,.text-indent"
      },
      tags = _STYLE_TAG_MAP;
    _each(tags, function(key, val) {
      map[key] = "*";
    });
    this.remove(map);
    return this.select();
  },
  inserthtml: function(val, quickMode) {
    var self = this,
      range = self.range;
    if (val === "") {
      return self;
    }
    //if (_inPreElement(K(range.startContainer))) {
    //	return self;
    //}
    // IE专用，优化性能
    function pasteHtml(range, val) {
      val =
        '<img id="__kindeditor_temp_tag__" width="0" height="0" style="display:none;" />' +
        val;
      var rng = range.get();
      if (rng.item) {
        rng.item(0).outerHTML = val;
      } else {
        rng.pasteHTML(val);
      }
      var temp = range.doc.getElementById("__kindeditor_temp_tag__");
      temp.parentNode.removeChild(temp);
      var newRange = _toRange(rng);
      range.setEnd(newRange.endContainer, newRange.endOffset);
      range.collapse(false);
      self.select(false);
    }
    // 全浏览器兼容，在IE上速度慢
    function insertHtml(range, val) {
      var doc = range.doc,
        frag = doc.createDocumentFragment();
      K("@" + val, doc).each(function() {
        frag.appendChild(this);
      });
      range.deleteContents();
      range.insertNode(frag);
      range.collapse(false);
      self.select(false);
    }
    if (_IERANGE && quickMode) {
      try {
        pasteHtml(range, val);
      } catch (e) {
        insertHtml(range, val);
      }
      return self;
    }
    insertHtml(range, val);
    return self;
  },
  hr: function() {
    return this.inserthtml("<hr />");
  },
  print: function() {
    this.win.print();
    return this;
  },
  insertimage: function(url, title, width, height, border, align) {
    title = _undef(title, "");
    border = _undef(border, 0);
    var html =
      '<img src="' + _escape(url) + '" data-ke-src="' + _escape(url) + '" ';
    if (width) {
      html += 'width="' + _escape(width) + '" ';
    }
    if (height) {
      html += 'height="' + _escape(height) + '" ';
    }
    if (title) {
      html += 'title="' + _escape(title) + '" ';
    }
    if (align) {
      html += 'align="' + _escape(align) + '" ';
    }
    html += 'alt="' + _escape(title) + '" ';
    html += "/>";
    return this.inserthtml(html);
  },
  createlink: function(url, type) {
    var self = this,
      doc = self.doc,
      range = self.range;
    self.select();
    var a = self.commonNode({ a: "*" });
    if (a && !range.isControl()) {
      range.selectNode(a.get());
      self.select();
    }
    var html =
      '<a href="' + _escape(url) + '" data-ke-src="' + _escape(url) + '" ';
    if (type) {
      html += ' target="' + _escape(type) + '"';
    }
    if (range.collapsed) {
      html += ">" + _escape(url) + "</a>";
      return self.inserthtml(html);
    }
    if (range.isControl()) {
      var node = K(range.startContainer.childNodes[range.startOffset]);
      html += "></a>";
      node.after(K(html, doc));
      node.next().append(node);
      range.selectNode(node[0]);
      return self.select();
    }
    function setAttr(node, url, type) {
      K(node)
        .attr("href", url)
        .attr("data-ke-src", url);
      if (type) {
        K(node).attr("target", type);
      } else {
        K(node).removeAttr("target");
      }
    }
    // Bugfix: https://github.com/kindsoft/kindeditor/issues/117
    // [IE] 当两个A标签并排在一起中间没有别的内容，修改后面的链接地址时，前面的链接地址也被改掉。
    var sc = range.startContainer,
      so = range.startOffset,
      ec = range.endContainer,
      eo = range.endOffset;
    if (sc.nodeType == 1 && sc === ec && so + 1 === eo) {
      var child = sc.childNodes[so];
      if (child.nodeName.toLowerCase() == "a") {
        setAttr(child, url, type);
        return self;
      }
    }
    _nativeCommand(doc, "createlink", "__kindeditor_temp_url__");
    K('a[href="__kindeditor_temp_url__"]', doc).each(function() {
      setAttr(this, url, type);
    });
    return self;
  },
  unlink: function() {
    var self = this,
      doc = self.doc,
      range = self.range;
    self.select();
    if (range.collapsed) {
      var a = self.commonNode({ a: "*" });
      if (a) {
        range.selectNode(a.get());
        self.select();
      }
      _nativeCommand(doc, "unlink", null);
      if (_WEBKIT && K(range.startContainer).name === "img") {
        var parent = K(range.startContainer).parent();
        if (parent.name === "a") {
          parent.remove(true);
        }
      }
    } else {
      _nativeCommand(doc, "unlink", null);
    }
    return self;
  }
});

_each(
  (
    "formatblock,selectall,justifyleft,justifycenter,justifyright,justifyfull,insertorderedlist," +
    "insertunorderedlist,indent,outdent,subscript,superscript"
  ).split(","),
  function(i, name) {
    KCmd.prototype[name] = function(val) {
      var self = this;
      self.select();
      _nativeCommand(self.doc, name, val);
      // Bugfix: [IE] 先选中图片后居中，再左对齐，光标跳到顶部
      if (
        _IERANGE &&
        _inArray(
          name,
          "justifyleft,justifycenter,justifyright,justifyfull".split(",")
        ) >= 0
      ) {
        self.selection();
      }
      // 在webkit和firefox上需要重新选取range，否则有时候会报错
      if (
        !_IERANGE ||
        _inArray(
          name,
          "formatblock,selectall,insertorderedlist,insertunorderedlist".split(
            ","
          )
        ) >= 0
      ) {
        self.selection();
      }
      return self;
    };
  }
);

_each("cut,copy,paste".split(","), function(i, name) {
  KCmd.prototype[name] = function() {
    var self = this;
    if (!self.doc.queryCommandSupported(name)) {
      throw "not supported";
    }
    self.select();
    _nativeCommand(self.doc, name, null);
    return self;
  };
});

function _cmd(mixed) {
  // mixed is a node
  if (mixed.nodeName) {
    var doc = _getDoc(mixed);
    mixed = _range(doc)
      .selectNodeContents(doc.body)
      .collapse(false);
  }
  // mixed is a KRange
  return new KCmd(mixed);
}

K.CmdClass = KCmd;
K.cmd = _cmd;
